/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package javax.faces.model;

import javax.faces.FacesException;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.ResultSetMetaData;
import java.util.*;

/**
 * see Javadoc of <a href="http://java.sun.com/javaee/javaserverfaces/1.2/docs/api/index.html">JSF Specification</a>
 *
 * @author Thomas Spiegl (latest modification by $Author: skitching $)
 * @author Martin Marinschek
 * @version $Revision: 676298 $ $Date: 2008-07-13 18:31:48 +0800 (星期日, 13 七月 2008) $
 */
public class ResultSetDataModel extends DataModel
{
    // FIELDS

    private int _currentIndex = -1;

    /**
     * The ResultSet being wrapped by this DataModel.
     */
    private ResultSet _resultSet = null;

    /**
     * The MetaData of the ResultSet being wrapped by this DataModel.
     */
    private ResultSetMetaData _resultSetMetadata = null;


    /**
     *  Indicator for an updated row at the current position.
     */
    private boolean _currentRowUpdated = false;

    // CONSTRUCTORS
    public ResultSetDataModel()
    {
        this(null);
    }

    public ResultSetDataModel(ResultSet resultSet)
    {

        super();
        setWrappedData(resultSet);

    }

    /** We don't know how many rows the result set has without scrolling
     * through the whole thing.
     */
    public int getRowCount()
    {
        return -1;
    }

    /** Get the actual data of this row
     *  wrapped into a map.
     *  The specification is very strict about what has to be
     *  returned from here, so check the spec before
     *  modifying anything here.
     */
    public Object getRowData()
    {
        if (_resultSet == null)
        {
            return null;
        }
        else if (!isRowAvailable())
        {
            throw new IllegalArgumentException(
                    "the requested row is not available in the ResultSet - you have scrolled beyond the end.");
        }

        try
        {
            return new WrapResultSetMap(String.CASE_INSENSITIVE_ORDER);
        }
        catch (SQLException e)
        {
            throw new FacesException(e);
        }
    }

    public int getRowIndex()
    {
        return _currentIndex;
    }

    public Object getWrappedData()
    {
        return _resultSet;
    }

    public boolean isRowAvailable()
    {
        if (_resultSet == null)
        {
            return false;
        }
        else if (_currentIndex < 0)
        {
            return false;
        }

        try
        {
            return _resultSet.absolute(_currentIndex + 1);
        }
        catch (SQLException e)
        {
            throw new FacesException(e);
        }
    }

    public void setRowIndex(int rowIndex)
    {
        if (rowIndex < -1)
        {
            throw new IllegalArgumentException(
                    "you cannot set the rowIndex to anything less than 0");
        }

        // Handle the case of an updated row
        if (_currentRowUpdated && _resultSet != null)
        {
            try
            {
                if (!_resultSet.rowDeleted())
                    _resultSet.updateRow();

                setCurrentRowUpdated(false);
            }
            catch (SQLException e)
            {
                throw new FacesException(e);
            }
        }

        int old = _currentIndex;
        _currentIndex = rowIndex;

        //if no underlying data has been set, the listeners
        //need not be notified
        if (_resultSet == null)
            return;

        //Notify all listeners of the upated row
        DataModelListener [] listeners = getDataModelListeners();

        if ((old != _currentIndex) && (listeners != null))
        {
            Object rowData = null;

            if (isRowAvailable())
            {
                rowData = getRowData();
            }

            DataModelEvent event =
                new DataModelEvent(this, _currentIndex, rowData);

            int n = listeners.length;

            for (int i = 0; i < n; i++)
            {
                if (listeners[i]!=null)
                {
                    listeners[i].rowSelected(event);
                }
            }
        }
    }

    public void setWrappedData(Object data)
    {
        if (data == null)
        {
            _resultSetMetadata = null;
            _resultSet = null;
            setRowIndex(-1);
        }
        else
        {
            _resultSetMetadata = null;
            _resultSet = (ResultSet) data;
            _currentIndex = -1;
            setRowIndex(0);
        }
    }

    private ResultSetMetaData getResultSetMetadata()
    {
        if (_resultSetMetadata == null)
        {
            try
            {
                _resultSetMetadata = _resultSet.getMetaData();
            }
            catch (SQLException e)
            {
                throw new FacesException(e);
            }
        }

        return _resultSetMetadata;
    }

    private void setCurrentRowUpdated(boolean currentRowUpdated)
    {
        _currentRowUpdated = currentRowUpdated;
    }

    /* A map wrapping the result set and calling
    * the corresponding operations on the result set,
    * first setting the correct row index.
    */
    private class WrapResultSetMap extends TreeMap
    {
        private static final long serialVersionUID = -4321143404567038922L;
        private int _currentIndex;

        public WrapResultSetMap(Comparator comparator) throws SQLException
        {
            super(comparator);

            _currentIndex = ResultSetDataModel.this._currentIndex;

            _resultSet.absolute(_currentIndex + 1);

            int columnCount = getResultSetMetadata().getColumnCount();

            for (int i = 1; i <= columnCount; i++) {
                super.put(getResultSetMetadata().getColumnName(i),
                          getResultSetMetadata().getColumnName(i));
            }
        }

        public void clear()
        {
            throw new UnsupportedOperationException(
                    "It is not allowed to remove from this map");
        }

        public boolean containsValue(Object value)
        {
            Set<Object> keys = keySet();
            for (Iterator<Object> iterator = keys.iterator(); iterator.hasNext();) {
                Object object = get(iterator.next());
                if (object == null) {
                    return value == null;
                }
                if (object.equals(value)) {
                    return true;
                }

            }
            return false;
        }

        public Set entrySet()
        {
            return new WrapResultSetEntries(this);
        }

        public Object get(Object key)
        {
            if (!containsKey(key))
                return null;

            return basicGet(key);
        }


        private Object basicGet(Object key)
        {  //#################################################### remove
            try
            {
                _resultSet.absolute(_currentIndex + 1);

                return _resultSet.getObject((String) getUnderlyingKey(key));

            }
            catch (SQLException e)
            {
                throw new FacesException(e);
            }
        }


        public Set<Object> keySet()
        {
            return new WrapResultSetKeys(this);
        }

        public Object put(Object key, Object value)
        {
            if (!containsKey(key))
                throw new IllegalArgumentException(
                        "underlying result set does not provide this key");

            if (!(key instanceof String))
                throw new IllegalArgumentException(
                        "key must be of type 'String', is of type : "+(key==null?"null":key.getClass().getName()));

            try
            {
                _resultSet.absolute(_currentIndex + 1);

                Object oldValue = _resultSet.getObject((String) getUnderlyingKey(key));

                if(oldValue==null?value==null:oldValue.equals(value))
                    return oldValue;

                _resultSet.updateObject((String) getUnderlyingKey(key), value);

                setCurrentRowUpdated(true);

                return oldValue;
            }
            catch (SQLException e)
            {
                throw new FacesException(e);
            }
        }

        public void putAll(Map map)
        {
            for (Iterator i = map.entrySet().iterator(); i.hasNext(); )
            {
                Map.Entry entry = (Map.Entry) i.next();
                put(entry.getKey(), entry.getValue());
            }
        }

        public Object remove(Object key)
        {
            throw new UnsupportedOperationException(
                    "It is not allowed to remove entries from this set.");
        }

        public Collection<Object> values()
        {
            return new WrapResultSetValues(this);
        }

        Object getUnderlyingKey(Object key)
        {
            return super.get(key);
        }

        Iterator getUnderlyingKeys()
        {
            return super.keySet().iterator();
        }

    }

    private static class WrapResultSetEntries extends AbstractSet
    {
        private WrapResultSetMap _wrapMap;

        public WrapResultSetEntries(WrapResultSetMap wrapMap)
        {
            _wrapMap = wrapMap;
        }

        public boolean add(Object o)
        {
            throw new UnsupportedOperationException(
                    "it is not allowed to add to this set");
        }

        public boolean addAll(Collection c)
        {
            throw new UnsupportedOperationException(
                    "it is not allowed to add to this set");
        }

        public void clear()
        {
            throw new UnsupportedOperationException(
                    "it is not allowed to remove from this set"
            );
        }

        public boolean contains(Object o)
        {
            if (o == null)
                throw new NullPointerException();
            if (!(o instanceof Map.Entry))
                return false;

            Map.Entry e = (Map.Entry) o;
            Object key = e.getKey();

            if (!_wrapMap.containsKey(key))
                return false;

            Object value = e.getValue();
            Object cmpValue = _wrapMap.get(key);

            return value==null?cmpValue==null:value.equals(cmpValue);
        }

        public boolean isEmpty()
        {
            return _wrapMap.isEmpty();
        }

        public Iterator iterator()
        {
            return new WrapResultSetEntriesIterator(_wrapMap);
        }

        public boolean remove(Object o)
        {
            throw new UnsupportedOperationException(
                    "it is not allowed to remove from this set");
        }

        public boolean removeAll(Collection c)
        {
            throw new UnsupportedOperationException(
                    "it is not allowed to remove from this set");
        }

        public boolean retainAll(Collection c)
        {
            throw new UnsupportedOperationException(
                    "it is not allowed to remove from this set");
        }

        public int size()
        {
            return _wrapMap.size();
        }
    }


    private static class WrapResultSetEntriesIterator implements Iterator
    {

        private WrapResultSetMap _wrapMap = null;
        private Iterator<Object> _keyIterator = null;

        public WrapResultSetEntriesIterator(WrapResultSetMap wrapMap)
        {
            _wrapMap = wrapMap;
            _keyIterator = _wrapMap.keySet().iterator();
        }

        public boolean hasNext()
        {
            return _keyIterator.hasNext();
        }

        public Object next()
        {
            return new WrapResultSetEntry(_wrapMap, _keyIterator.next());
        }

        public void remove()
        {
            throw new UnsupportedOperationException(
                    "It is not allowed to remove from this iterator"
            );
        }

    }

    private static class WrapResultSetEntry implements Map.Entry {

        private WrapResultSetMap _wrapMap;
        private Object _entryKey;

        public WrapResultSetEntry(WrapResultSetMap wrapMap, Object entryKey)
        {
            _wrapMap = wrapMap;
            _entryKey = entryKey;
        }


        public boolean equals(Object o)
        {
            if (o == null)
                return false;

            if (!(o instanceof Map.Entry))
                return false;

            Map.Entry cmpEntry = (Map.Entry) o;

            if(_entryKey ==null?cmpEntry.getKey()!=null:
                    !_entryKey.equals(cmpEntry.getKey()))
                return false;

            Object value = _wrapMap.get(_entryKey);
            Object cmpValue = cmpEntry.getValue();

            return value==null?cmpValue!=null:value.equals(cmpValue);
        }

        public Object getKey()
        {
            return _entryKey;
        }

        public Object getValue()
        {
            return _wrapMap.get(_entryKey);
        }

        public int hashCode()
        {
            int result;
            result = (_entryKey != null ? _entryKey.hashCode() : 0);
            result = 29 * result + (_wrapMap.get(_entryKey) != null ?
                    _wrapMap.get(_entryKey).hashCode() : 0);
            return result;
        }

        public Object setValue(Object value)
        {
            Object oldValue = _wrapMap.get(_entryKey);
            _wrapMap.put(_entryKey, value);
            return oldValue;
        }
    }

    private static class WrapResultSetKeys extends AbstractSet
    {
        private WrapResultSetMap _wrapMap;

        public WrapResultSetKeys(WrapResultSetMap wrapMap) {
            _wrapMap = wrapMap;
        }

        public boolean add(Object o)
        {
            throw new UnsupportedOperationException(
                    "It is not allowed to add to this set");
        }

        public boolean addAll(Collection c)
        {
            throw new UnsupportedOperationException(
                    "It is not allowed to add to this set");
        }

        public void clear()
        {
            throw new UnsupportedOperationException(
                    "It is not allowed to remove from this set"
            );
        }

        public boolean contains(Object obj)
        {
            return _wrapMap.containsKey(obj);
        }

        public boolean isEmpty()
        {
            return _wrapMap.isEmpty();
        }

        public Iterator iterator()
        {
            return new WrapResultSetKeysIterator(_wrapMap);
        }

        public boolean remove(Object o)
        {
            throw new UnsupportedOperationException(
                    "It is not allowed to remove from this set");
        }

        public boolean removeAll(Collection c)
        {
            throw new UnsupportedOperationException(
                    "It is not allowed to remove from this set");
        }

        public boolean retainAll(Collection c)
        {
            throw new UnsupportedOperationException(
                    "It is not allowed to remove from this set");
        }

        public int size()
        {
            return _wrapMap.size();
        }
    }

    private static class WrapResultSetKeysIterator implements Iterator
    {
        private Iterator _keyIterator = null;

        public WrapResultSetKeysIterator(WrapResultSetMap map)
        {
            _keyIterator = map.getUnderlyingKeys();
        }

        public boolean hasNext()
        {
            return _keyIterator.hasNext();
        }

        public Object next()
        {
            return _keyIterator.next();
        }

        public void remove()
        {
            throw new UnsupportedOperationException(
                    "it is not allowed to remove from this iterator");
        }

    }

    private static class WrapResultSetValues extends AbstractCollection
    {
        private WrapResultSetMap _wrapMap;

        public WrapResultSetValues(WrapResultSetMap wrapMap)
        {
            _wrapMap = wrapMap;
        }

        public boolean add(Object o)
        {
            throw new UnsupportedOperationException(
                    "it is not allowed to add to this collection"
            );
        }

        public boolean addAll(Collection c)
        {
            throw new UnsupportedOperationException(
                "it is not allowed to add to this collection"
            );
        }

        public void clear()
        {
            throw new UnsupportedOperationException(
                "it is not allowed to remove from this collection"
            );
        }

        public boolean contains(Object value)
        {
            return _wrapMap.containsValue(value);
        }

        public Iterator iterator()
        {
            return new WrapResultSetValuesIterator(_wrapMap);
        }

        public boolean remove(Object o)
        {
            throw new UnsupportedOperationException();
        }

        public boolean removeAll(Collection c)
        {
            throw new UnsupportedOperationException(
                    "it is not allowed to remove from this collection");
        }

        public boolean retainAll(Collection c)
        {
            throw new UnsupportedOperationException(
                    "it is not allowed to remove from this collection");
        }

        public int size()
        {
            return _wrapMap.size();
        }

    }


    private static class WrapResultSetValuesIterator implements Iterator
    {

        private WrapResultSetMap _wrapMap;
        private Iterator<Object> _keyIterator;

        public WrapResultSetValuesIterator(WrapResultSetMap wrapMap)
        {
            _wrapMap = wrapMap;
            _keyIterator = _wrapMap.keySet().iterator();
        }

        public boolean hasNext()
        {
            return _keyIterator.hasNext();
        }

        public Object next()
        {
            return _wrapMap.get(_keyIterator.next());
        }

        public void remove()
        {
            throw new UnsupportedOperationException(
                    "it is not allowed to remove from this map"
            );
        }

    }

}
